using System;
using System.Collections.Generic;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /// <summary>
    /// A unary operator that uses the string value of an expression as a hash key.
    /// It associates with the <see cref="Identifier"/> or <see cref="Dot"/> to its left.
    /// </summary>
    internal sealed class DynamicKeyName : Expression
    {
        private readonly Expression _nameExpression;
        private readonly Expression _target;

        internal DynamicKeyName(Expression target, Expression nameExpression)
        {
            _target = target;
            _nameExpression = nameExpression;
        }

        protected override ITemplateModel _getAsTemplateModel(Environment env)
        {
            ITemplateModel targetModel = _target.GetAsTemplateModel(env);
            AssertNonNull(targetModel, _target, env);
            if (_nameExpression is Range)
            {
                return DealWithRangeKey(targetModel, (Range) _nameExpression, env);
            }
            ITemplateModel keyModel = _nameExpression.GetAsTemplateModel(env);
            if (keyModel == null)
            {
                if (env.IsClassicCompatible())
                {
                    keyModel = TemplateScalarModel.EmptyString;
                }
                else
                {
                    AssertNonNull(keyModel, _nameExpression, env);
                }
            }
            if (keyModel is ITemplateNumberModel)
            {
                int index = EvaluationUtil.GetNumber(keyModel, _nameExpression, env).IntValue();
                return DealWithNumericalKey(targetModel, index, env);
            }
            if (keyModel is ITemplateScalarModel)
            {
                string key = EvaluationUtil.GetString((ITemplateScalarModel) keyModel, _nameExpression, env);
                return DealWithStringKey(targetModel, key, env);
            }
            throw InvalidTypeException(keyModel, _nameExpression, env, "number, range, or string");
        }


        private ITemplateModel DealWithNumericalKey(
            ITemplateModel targetModel,
            int index,
            Environment env)
        {
            if (targetModel is ITemplateSequenceModel)
            {
                var tsm = (ITemplateSequenceModel) targetModel;
                int size = int.MaxValue;
                try
                {
                    size = tsm.Size();
                }
                catch (Exception)
                {
                }
                return index < size ? tsm.Get(index) : null;
            }

            try
            {
                string s = _target.GetStringValue(env);
                try
                {
                    return new SimpleScalar(s.Substring(index, 1));
                }
                catch (Exception re)
                {
                    throw new TemplateException("", re, env);
                }
            }
            catch (NonStringException)
            {
                throw InvalidTypeException(targetModel, _target, env, "number, sequence, or string");
            }
        }


        private ITemplateModel DealWithStringKey(
            ITemplateModel targetModel,
            string key,
            Environment env)
        {
            if (targetModel is ITemplateHashModel)
            {
                return ((ITemplateHashModel) targetModel).Get(key);
            }
            throw InvalidTypeException(targetModel, _target, env, "hash");
        }

        private ITemplateModel DealWithRangeKey(
            ITemplateModel targetModel,
            Range range,
            Environment env)
        {
            int start = EvaluationUtil.GetNumber(range.Left, env).IntValue();
            int end = 0;
            bool hasRhs = range.HasRhs();
            if (hasRhs)
            {
                end = EvaluationUtil.GetNumber(range.Right, env).IntValue();
            }
            if (targetModel is ITemplateSequenceModel)
            {
                var sequence = (ITemplateSequenceModel) targetModel;
                if (!hasRhs) end = sequence.Size() - 1;
                if (start < 0)
                {
                    string msg = range.Left.GetStartLocation() + "\nNegative starting index for range, is " + range;
                    throw new TemplateException(msg, env);
                }
                if (end < 0)
                {
                    string msg = range.Left.GetStartLocation() + "\nNegative ending index for range, is " + range;
                    throw new TemplateException(msg, env);
                }
                if (start >= sequence.Size())
                {
                    string msg = range.Left.GetStartLocation()
                                 + "\nLeft side index of range out of bounds, is " + start
                                 + ", but the sequence has only " + sequence.Size() + " element(s) "
                                 + "(note that indices are 0 based, and ranges are inclusive).";
                    throw new TemplateException(msg, env);
                }
                if (end >= sequence.Size())
                {
                    string msg = range.Right.GetStartLocation()
                                 + "\nRight side index of range out of bounds, is " + end
                                 + ", but the sequence has only " + sequence.Size() + " element(s)."
                                 + "(note that indices are 0 based, and ranges are inclusive).";
                    throw new TemplateException(msg, env);
                }
                var list = new List<ITemplateModel>(1 + Math.Abs(start - end));
                if (start > end)
                {
                    for (int i = start; i >= end; i--)
                    {
                        list.Add(sequence.Get(i));
                    }
                }
                else
                {
                    for (int i = start; i <= end; i++)
                    {
                        list.Add(sequence.Get(i));
                    }
                }
                return new SimpleSequence(list);
            }

            try
            {
                string s = _target.GetStringValue(env);
                if (!hasRhs) end = s.Length - 1;
                if (start < 0)
                {
                    string msg = range.Left.GetStartLocation() + "\nNegative starting index for range " + range + " : " +
                                 start;
                    throw new TemplateException(msg, env);
                }
                if (end < 0)
                {
                    string msg = range.Left.GetStartLocation() + "\nNegative ending index for range " + range + " : " +
                                 end;
                    throw new TemplateException(msg, env);
                }
                if (start > s.Length)
                {
                    string msg = range.Left.GetStartLocation()
                                 + "\nLeft side of range out of bounds, is: " + start
                                 + "\nbut string " + targetModel + " has " + s.Length + " elements.";
                    throw new TemplateException(msg, env);
                }
                if (end > s.Length)
                {
                    string msg = range.Right.GetStartLocation()
                                 + "\nRight side of range out of bounds, is: " + end
                                 + "\nbut string " + targetModel + " is only " + s.Length + " characters.";
                    throw new TemplateException(msg, env);
                }
                try
                {
                    return new SimpleScalar(s.Substring(start, 1));
                }
                catch (Exception re)
                {
                    string msg = "Error " + GetStartLocation();
                    throw new TemplateException(msg, re, env);
                }
            }
            catch (NonStringException)
            {
                throw InvalidTypeException(_target.GetAsTemplateModel(env), _target, env, "number, scalar, or sequence");
            }
        }

        public override string GetCanonicalForm()
        {
            return _target.GetCanonicalForm()
                   + "["
                   + _nameExpression.GetCanonicalForm()
                   + "]";
        }

        internal override bool IsLiteral()
        {
            return ConstantValue != null || (_target.IsLiteral() && _nameExpression.IsLiteral());
        }

        protected override Expression DeepCloneImpl(string name, Expression subst)
        {
            return new DynamicKeyName(_target.DeepClone(name, subst), _nameExpression.DeepClone(name, subst));
        }
    }
}